﻿#region File Info Header
/*________________________________________________________________________________________

  Copyright (C) 2011 Jason Zhang, eagleboost@msn.com

  * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
  * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
  * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.

________________________________________________________________________________________*/
#endregion File Info Header

namespace EagleBoost.Wpf.Presentation.Behaviors
{
  using System.Text.RegularExpressions;
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Input;
  using Infra.Extensions;

  /// <summary>
  /// TextBoxMaskBehavior
  /// 1. Extending regex mask for text box for inputs validation
  /// 2. Handling usual typing as well as pasting
  /// </summary>
  public static class TextBoxMaskBehavior
  {
    #region MaskProperty
    public static readonly DependencyProperty MaskProperty =
        DependencyProperty.RegisterAttached("Mask", typeof(string), typeof(TextBoxMaskBehavior),
        new UIPropertyMetadata(null, OnMaskChanged));

    public static string GetMask(DependencyObject obj)
    {
      return (string)obj.GetValue(MaskProperty);
    }

    public static void SetMask(DependencyObject obj, string value)
    {
      obj.SetValue(MaskProperty, value);
    }
    #endregion

    #region MaskRegexProperty
    internal static readonly DependencyProperty MaskRegexProperty =
        DependencyProperty.RegisterAttached("MaskRegex", typeof(Regex), typeof(TextBoxMaskBehavior),
        new UIPropertyMetadata(null, OnMaskChanged));

    internal static Regex GetMaskRegex(DependencyObject obj)
    {
      return (Regex)obj.GetValue(MaskRegexProperty);
    }

    internal static void SetMaskRegex(DependencyObject obj, Regex value)
    {
      obj.SetValue(MaskRegexProperty, value);
    }
    #endregion

    #region Event Handlers
    private static void OnMaskChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
      var textBox = obj as TextBox;
      if (textBox != null)
      {
        var mask = args.NewValue as string;
        textBox.PreviewTextInput -= TextBoxPreviewTextInput;
        textBox.PreviewKeyDown -= TextBoxPreviewKeyDown;
        DataObject.RemovePastingHandler(textBox, PastingHandler);

        if (!string.IsNullOrEmpty(mask))
        {
          textBox.SetValue(MaskProperty, mask);
          SetMaskRegex(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));

          textBox.PreviewTextInput += TextBoxPreviewTextInput;
          textBox.PreviewKeyDown += TextBoxPreviewKeyDown;
          DataObject.AddPastingHandler(textBox, PastingHandler);
        }
      }
    }

    /// <summary>
    /// Handle PreviewTextInput and ignore invalid inputs
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private static void TextBoxPreviewTextInput(object sender, TextCompositionEventArgs args)
    {
      var textBox = sender as TextBox;
      var maskRegex = GetMaskRegex(textBox);

      if (maskRegex != null)
      {
        var proposedText = GetProposedText(textBox, args.Text);
        if (!maskRegex.IsExactMatch(proposedText))
        {
          args.Handled = true;
        }
      }
    }

    /// <summary>
    /// Hande PreviewKeyDown and ignore space
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private static void TextBoxPreviewKeyDown(object sender, KeyEventArgs args)
    {
      var textBox = sender as TextBox;
      var maskRegex = GetMaskRegex(textBox);

      if (maskRegex != null)
      {
        //pressing space doesn't raise PreviewTextInput so we need to handle it explicitly
        if (args.Key == Key.Space)
        {
          var proposedText = GetProposedText(textBox, " ");

          if (!maskRegex.IsExactMatch(proposedText))
          {
            args.Handled = true;
          }
        }
      }
    }

    /// <summary>
    /// Handle pasting, cancel the action if clipboard contains invalid chars
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private static void PastingHandler(object sender, DataObjectPastingEventArgs args)
    {
      var textBox = sender as TextBox;
      var maskRegex = GetMaskRegex(textBox);

      if (maskRegex != null)
      {
        if (args.DataObject.GetDataPresent(typeof(string)))
        {
          var pastedText = args.DataObject.GetData(typeof(string)) as string;
          var proposedText = GetProposedText(textBox, pastedText);

          if (!maskRegex.IsExactMatch(proposedText))
          {
            args.CancelCommand();
          }
        }
        else
        {
          args.CancelCommand();
        }
      }
    }

    private static string GetProposedText(TextBox textBox, string newText)
    {
      var text = textBox.Text;
      if (textBox.SelectionStart != -1)
      {
        text = text.Remove(textBox.SelectionStart, textBox.SelectionLength);
      }

      text = text.Insert(textBox.CaretIndex, newText);
      return text;
    }
    #endregion
  }
}
